﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection.Metadata;
using System.Runtime.CompilerServices;
using System.Security.Cryptography;
using System.Text;
using dnlib.DotNet;
using dnlib.DotNet.Emit;

namespace Natsu.Compiler
{
    class Program
    {
        private static readonly string[] _modulePaths = new[]
        {
            @"..\..\..\..\..\out\bin\netcoreapp3.0\Chino.Kernel.dll",
            @"..\..\..\..\..\out\bin\netcoreapp3.0\Chino.Core.dll",
            @"..\..\..\..\..\out\bin\netcoreapp3.0\Chino.Threading.dll",
            @"..\..\..\..\..\out\bin\netcoreapp3.0\Chino.IO.dll",
            @"..\..\..\..\..\out\bin\netcoreapp3.0\Chino.Chip.K210.dll",
            @"..\..\..\..\..\out\bin\netcoreapp3.0\Chino.Chip.Emulator.dll",
            @"..\..\..\..\..\out\bin\netcoreapp3.0\Chino.Interop.dll",
            @"..\..\..\..\..\out\bin\netcoreapp3.0\Chino.Apps.Shell.dll",
            @"..\..\..\..\..\out\bin\netcoreapp3.0\System.Private.CoreLib.dll",
            @"..\..\..\..\..\out\bin\netcoreapp3.0\System.Console.dll",
            @"..\..\..\..\..\out\bin\netcoreapp3.0\System.Collections.dll",
            @"..\..\..\..\..\out\bin\netcoreapp3.0\System.Memory.dll",
            @"..\..\..\..\..\out\bin\netcoreapp3.0\System.Runtime.dll",
            @"..\..\..\..\..\out\bin\netcoreapp3.0\System.Runtime.Extensions.dll",
            @"..\..\..\..\..\out\bin\netcoreapp3.0\System.Diagnostics.Debug.dll",
            @"..\..\..\..\..\out\bin\netcoreapp3.0\System.Diagnostics.Process.dll",
            @"..\..\..\..\..\out\bin\netcoreapp3.0\System.Runtime.InteropServices.dll",
            @"..\..\..\..\..\out\bin\netcoreapp3.0\System.Threading.dll",
            @"..\..\..\..\..\out\bin\netcoreapp3.0\System.Threading.Thread.dll",
            Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), @".nuget\packages\bitfields\0.1.0\lib\netstandard1.0\BitFields.dll")
        };

        static void Main(string[] args)
        {
            var ctx = ModuleDef.CreateModuleContext();
            foreach (var path in _modulePaths)
            {
                var module = ModuleDefMD.Load(path, ctx);
                var generator = new Generator(module);
                generator.Generate();
            }
        }
    }

    class Generator
    {
        private readonly ModuleDefMD _module;
        private readonly Dictionary<TypeDef, TypeDesc> _typeDescs = new Dictionary<TypeDef, TypeDesc>();
        private readonly List<TypeDesc> _sortedTypeDescs = new List<TypeDesc>();
        private readonly CorLibTypes _corLibTypes;
        private TypeDesc _szArrayType;
        private List<string> _userStrings = new List<string>();
        private const string DigestHeader = "// Generated by NatsuCLR Compiler, digest: ";

        public Generator(ModuleDefMD module)
        {
            _module = module;
            _corLibTypes = new CorLibTypes(module);
        }

        public void Generate()
        {
            var outputPath = Path.GetFullPath(@"..\..\..\..\Native\Generated");
            Directory.CreateDirectory(outputPath);
            string digest;

            using (var sha256 = SHA256.Create())
            {
                digest = Convert.ToBase64String(sha256.ComputeHash(File.ReadAllBytes(_module.Location)));
#if true
                if (HasOutputUptodate(Path.Combine(outputPath, $"{_module.Assembly.Name}.h"), digest))
                    return;
#endif
            }

            foreach (var type in _module.GetTypes())
            {
                var typeDesc = new TypeDesc(type);
                _typeDescs.Add(type, typeDesc);

                if (type.FullName == "System.SZArray`1")
                    _szArrayType = typeDesc;
            }

            SortTypes();

            using (var writer = new StreamWriter(Path.Combine(outputPath, $"{_module.Assembly.Name}.h"), false, Encoding.UTF8))
            {
                writer.WriteLine(DigestHeader + digest);
                writer.WriteLine("#pragma once");
                if (_module.Assembly.Name == "System.Private.CoreLib")
                {
                    writer.WriteLine("#include <natsu.typedef.h>");
                }
                else
                {
                    foreach (var ass in _module.GetAssemblyRefs())
                        writer.WriteLine($"#include <{ass.Name}.h>");
                }

                writer.WriteLine();

                writer.WriteLine($"namespace {TypeUtils.EscapeModuleName(_module)}");
                writer.WriteLine("{");
                WriteTypeForwards(writer);
                writer.WriteLine();
                WriteTypeForwardDeclares(writer);
                writer.WriteLine();
                WriteAssemblyEmbeddedCode(_module.Assembly, writer);
                writer.WriteLine();
                WriteTypeDeclares(writer);
                writer.WriteLine("}");
                writer.WriteLine();

                if (_module.Assembly.Name == "System.Private.CoreLib")
                {
                    writer.WriteLine("#include <natsu.runtime.h>");
                    writer.WriteLine();
                }
                else if (_module.Assembly.Name == "Chino.Core")
                {
                    writer.WriteLine("#include <chino.runtime.h>");
                    writer.WriteLine();
                }

                using (var writerSrc = new StreamWriter(Path.Combine(outputPath, $"{_module.Assembly.Name}.cpp"), false, Encoding.UTF8))
                {
                    writerSrc.WriteLine(DigestHeader + digest);
                    writerSrc.WriteLine($"#include \"{_module.Assembly.Name}.h\"");
                    writerSrc.WriteLine();

                    writerSrc.WriteLine($"namespace {TypeUtils.EscapeModuleName(_module)}");
                    writerSrc.WriteLine("{");
                    WriteTypeMethodsBody(writerSrc, false);
                    writerSrc.WriteLine("}");
                }

                var hBody = new StringWriter();

                hBody.WriteLine($"namespace {TypeUtils.EscapeModuleName(_module)}");
                hBody.WriteLine("{");
                WriteTypeMethodsBody(hBody, true);
                hBody.WriteLine("}");

                writer.WriteLine($"namespace {TypeUtils.EscapeModuleName(_module)}");
                writer.WriteLine("{");
                WriteUserStrings(writer);
                writer.WriteLine("}");

                writer.WriteLine();
                writer.WriteLine(hBody.ToString());
            }
        }

        private bool HasOutputUptodate(string file, string digest)
        {
            try
            {
                using (var sr = new StreamReader(file, Encoding.UTF8))
                {
                    var fileDigest = sr.ReadLine().Split(DigestHeader)[1];
                    return fileDigest == digest;
                }
            }
            catch (Exception)
            {
                return false;
            }
        }

        private void WriteAssemblyEmbeddedCode(AssemblyDef assembly, StreamWriter writer)
        {
            foreach (var att in assembly.CustomAttributes.FindAll("Natsu.AssemblyEmbeddedCodeAttribute"))
            {
                writer.WriteLine(att.ConstructorArguments[0].Value);
            }
        }

        private void WriteUserStrings(StreamWriter writer)
        {
            for (int i = 0; i < _userStrings.Count; i++)
            {
                writer.Ident(1).WriteLine($"static const constexpr auto user_string_{i} = ::natsu::make_string_literal(uR\"NS({_userStrings[i]})NS\");");
            }

            writer.WriteLine();
        }

        private void SortTypes()
        {
            foreach (var type in _typeDescs.Values)
            {
                foreach (var field in type.TypeDef.Fields)
                    AddTypeRef(type, field.FieldType, false);

                var baseType = GetTypeDef(GetBaseType(type.TypeDef));
                if (baseType != null)
                    AddTypeRef(type, baseType, true);

                foreach (var iface in type.TypeDef.Interfaces)
                {
                    var typeDef = GetTypeDef(iface.Interface);
                    AddTypeRef(type, typeDef, true);
                }
            }

            var visited = new HashSet<TypeDesc>();
            void VisitType(TypeDesc type)
            {
                if (visited.Add(type))
                {
                    foreach (var parent in type.UsedTypes)
                        VisitType(parent);
                    _sortedTypeDescs.Add(type);
                }
            }

            foreach (var type in _typeDescs.Values)
                VisitType(type);
        }

        private TypeDef GetTypeDef(ITypeDefOrRef type)
        {
            var typeDef = type as TypeDef;
            if (typeDef == null)
            {
                if (type is TypeSpec typeSpec)
                {
                    var cntSig = typeSpec.TypeSig;
                    while (true)
                    {
                        switch (cntSig.ElementType)
                        {
                            case ElementType.Class:
                                {
                                    var sig = cntSig.ToClassSig();
                                    return sig.TypeDef;
                                }
                            case ElementType.GenericInst:
                                {
                                    var sig = cntSig.ToGenericInstSig();
                                    cntSig = sig.GenericType;
                                    break;
                                }
                            default:
                                cntSig = cntSig.Next;
                                break;
                        }

                        if (typeDef != null)
                            return typeDef;

                        if (cntSig == null)
                            return null;
                    }
                }
            }

            return typeDef;
        }

        private ITypeDefOrRef GetBaseType(TypeDef type)
        {
            return type.BaseType;
        }

        private void AddTypeRef(TypeDesc declareDesc, TypeSig fieldType, bool force, bool isGenPara = false)
        {
            var cntSig = fieldType;
            while (cntSig != null)
            {
                switch (cntSig.ElementType)
                {
                    case ElementType.Void:
                    case ElementType.Var:
                    case ElementType.ByRef:
                    case ElementType.Ptr:
                    case ElementType.CModReqd:
                    case ElementType.Object:
                    case ElementType.Boolean:
                    case ElementType.Char:
                    case ElementType.I1:
                    case ElementType.U1:
                    case ElementType.I2:
                    case ElementType.U2:
                    case ElementType.I4:
                    case ElementType.U4:
                    case ElementType.I8:
                    case ElementType.U8:
                    case ElementType.R4:
                    case ElementType.R8:
                        break;
                    case ElementType.Class:
                        if (isGenPara)
                            AddTypeRef(declareDesc, cntSig.TryGetTypeDef(), true);
                        break;
                    case ElementType.String:
                    case ElementType.I:
                    case ElementType.U:
                        {
                            if (cntSig.TryGetTypeDef() != declareDesc.TypeDef)
                                AddTypeRef(declareDesc, cntSig.TryGetTypeDef(), force);
                        }
                        break;
                    case ElementType.ValueType:
                        AddTypeRef(declareDesc, cntSig.TryGetTypeDef(), force);
                        break;
                    case ElementType.SZArray:
                        if (_szArrayType != null)
                            AddTypeRef(declareDesc, _szArrayType.TypeDef, force);
                        break;
                    case ElementType.GenericInst:
                        {
                            var sig = cntSig.ToGenericInstSig();
                            AddTypeRef(declareDesc, sig.GenericType, force);
                            foreach (var arg in sig.GenericArguments)
                                AddTypeRef(declareDesc, arg, force, true);
                            break;
                        }
                    default:
                        throw new NotSupportedException();
                }

                cntSig = cntSig.Next;
            }
        }

        private void AddTypeRef(TypeDesc declareDesc, TypeDef typeDef, bool force)
        {
            if (typeDef != null && (force || typeDef.IsValueType || typeDef.ToTypeSig().ElementType == ElementType.String))
            {
                if (_typeDescs.TryGetValue(typeDef, out var targetDesc))
                {
                    if (declareDesc != targetDesc)
                    {
                        declareDesc.UsedTypes.Add(targetDesc);
                        targetDesc.UsedByTypes.Add(declareDesc);
                    }
                }
            }
        }

        private void WriteTypeForwards(StreamWriter writer)
        {
            var types = _module.ExportedTypes.Where(x => x.Attributes == TypeAttributes.Forwarder).ToList();
            var index = 0;
            foreach (var type in types)
            {
                WriteTypeForward(writer, 0, type);
                if (index++ != types.Count - 1)
                    writer.WriteLine();
            }

            if (types.Any())
                writer.WriteLine();
        }

        private void WriteTypeForward(StreamWriter writer, int ident, dnlib.DotNet.ExportedType type)
        {
            var nss = type.Namespace.Split('.', StringSplitOptions.RemoveEmptyEntries)
                .Select(TypeUtils.EscapeNamespaceName).ToList();

            writer.Ident(ident);
            foreach (var ns in nss)
                writer.Write($"namespace {ns} {{ ");

            var fowardName = TypeUtils.EscapeTypeName(type.ToTypeRef());
            if (type.Name.Contains("`"))
            {
                var types = int.Parse(type.Name.Substring(type.Name.IndexOf('`') + 1));
                var genDecl = $"<{string.Join(", ", Enumerable.Range(0, types).Select(x => "class T" + x))}>";
                var genImpl = $"<{string.Join(", ", Enumerable.Range(0, types).Select(x => "T" + x))}>";
                writer.Ident(ident).Write($"template {genDecl} ");
                writer.Ident(ident).Write($"using {TypeUtils.EscapeTypeName(type.FullName)} = {fowardName}{genImpl};");
            }
            else
            {
                writer.Ident(ident).Write($"using {TypeUtils.EscapeTypeName(type.FullName)} = {fowardName}; ");
            }

            foreach (var ns in nss)
                writer.Write(" }");
        }

        #region Forward Declares
        private void WriteTypeForwardDeclares(StreamWriter writer)
        {
            var types = _typeDescs.Values.ToList();
            var index = 0;
            foreach (var type in types)
            {
                WriteTypeForwardDeclare(writer, 0, type);
                if (index++ != types.Count - 1)
                    writer.WriteLine();
            }
        }

        private void WriteTypeForwardDeclare(StreamWriter writer, int ident, TypeDesc type)
        {
            var nss = TypeUtils.GetNamespace(type.TypeDef).Split('.', StringSplitOptions.RemoveEmptyEntries)
                .Select(TypeUtils.EscapeNamespaceName).ToList();

            writer.Ident(ident);
            foreach (var ns in nss)
                writer.Write($"namespace {ns} {{ ");

            if (type.TypeDef.HasGenericParameters)
            {
                var typeNames = type.TypeDef.GenericParameters.Select(x => "class " + x.Name.String).ToList();
                writer.Ident(ident).Write($"template <{string.Join(", ", typeNames)}> ");
            }

            writer.Ident(ident).Write($"struct {type.Name};");

            foreach (var ns in nss)
                writer.Write(" }");
        }
        #endregion

        #region Declares

        private void WriteTypeDeclares(StreamWriter writer)
        {
            var index = 0;
            foreach (var type in _sortedTypeDescs)
            {
                WriteTypeDeclare(writer, 0, type);
                if (index++ != _sortedTypeDescs.Count - 1)
                    writer.WriteLine();
            }
        }

        private void WriteTypeDeclare(StreamWriter writer, int ident, TypeDesc type)
        {
            bool hasStaticMember = false;

            var nss = TypeUtils.GetNamespace(type.TypeDef).Split('.', StringSplitOptions.RemoveEmptyEntries)
                .Select(TypeUtils.EscapeNamespaceName).ToList();

            writer.Ident(ident);
            foreach (var ns in nss)
                writer.Write($"namespace {ns} {{ ");

            writer.WriteLine();
            if (type.TypeDef.HasGenericParameters)
            {
                var typeNames = type.TypeDef.GenericParameters.Select(x => "class " + x.Name.String).ToList();
                writer.Ident(ident).WriteLine($"template <{string.Join(", ", typeNames)}> ");
            }

            writer.Ident(ident).Write($"struct {type.Name}");

            var baseType = GetBaseType(type.TypeDef);
            if (baseType != null)
                writer.WriteLine(" : public " + TypeUtils.EscapeTypeName(baseType));
            else
                writer.WriteLine();

            writer.Ident(ident).WriteLine("{");
            // ctor
            if (type.TypeDef.IsExplicitLayout)
            {
                writer.Ident(ident + 1).WriteLine($"{type.Name}() = default;");
                writer.WriteLine();
                writer.Ident(ident + 1).WriteLine($"{type.Name}(const {type.Name} &other) noexcept");
                writer.Ident(ident + 1).WriteLine("{");
                writer.Ident(ident + 2).WriteLine($"std::memcpy(this, &other, sizeof({type.Name}));");
                writer.Ident(ident + 1).WriteLine("}");
                writer.WriteLine();
                writer.Ident(ident + 1).WriteLine($"{type.Name}({type.Name} &&other) noexcept");
                writer.Ident(ident + 1).WriteLine("{");
                writer.Ident(ident + 2).WriteLine($"std::memcpy(this, &other, sizeof({type.Name}));");
                writer.Ident(ident + 1).WriteLine("}");
                writer.WriteLine();
                writer.Ident(ident + 1).WriteLine($"{type.Name}& operator=(const {type.Name} &other) noexcept");
                writer.Ident(ident + 1).WriteLine("{");
                writer.Ident(ident + 2).WriteLine($"std::memcpy(this, &other, sizeof({type.Name}));");
                writer.Ident(ident + 2).WriteLine("return *this;");
                writer.Ident(ident + 1).WriteLine("}");
                writer.WriteLine();
                writer.Ident(ident + 1).WriteLine($"{type.Name}& operator=({type.Name} &&other) noexcept");
                writer.Ident(ident + 1).WriteLine("{");
                writer.Ident(ident + 2).WriteLine($"std::memcpy(this, &other, sizeof({type.Name}));");
                writer.Ident(ident + 2).WriteLine("return *this;");
                writer.Ident(ident + 1).WriteLine("}");
                writer.WriteLine();
            }

            // TypeInfo
            WriteTypeInfo(writer, ident + 1, type);
            writer.WriteLine();
            // VTable
            WriteVTableDeclare(writer, ident + 1, type);
            writer.WriteLine();

            if (type.TypeDef.IsEnum)
            {
                writer.Ident(ident + 1).WriteLine($"enum : {TypeUtils.EscapeVariableTypeName(type.TypeDef.GetEnumUnderlyingType())}");
                writer.Ident(ident + 1).WriteLine("{");
                foreach (var field in type.TypeDef.Fields)
                {
                    if (field.HasConstant)
                    {
                        writer.Ident(ident + 2).WriteLine($"{TypeUtils.EscapeIdentifier(field.Name)} = {TypeUtils.LiteralConstant(field.Constant.Value)},");
                    }
                }
                writer.Ident(ident + 1).WriteLine("} value__;");
            }
            else
            {
                var hasSize = type.TypeDef.HasClassLayout && type.TypeDef.ClassLayout.ClassSize != 0;
                var fieldSize = new List<string> { "0" };
                if (type.TypeDef.IsExplicitLayout)
                {
                    ident += 1;
                    writer.Ident(ident).WriteLine($"union");
                    writer.Ident(ident).WriteLine("{");
                }
                foreach (var field in type.TypeDef.Fields)
                {
                    if (field.HasConstant)
                        WriteConstantField(writer, ident + 1, field);
                    else if (!field.IsStatic)
                        WriteField(writer, ident + 1, field);
                    else
                        hasStaticMember = true;

                    if (hasSize && !field.IsStatic)
                    {
                        if (field.FieldOffset.HasValue)
                        {
                            fieldSize.Add($"({field.FieldOffset.Value} + {TypeUtils.GetTypeSize(field.FieldType.ElementType)})");
                        }
                        else
                        {
                            fieldSize.Add(TypeUtils.GetTypeSize(field.FieldType.ElementType));
                        }
                    }
                }

                if (hasSize)
                {
                    if (type.TypeDef.IsExplicitLayout)
                    {
                        writer.Ident(ident + 1).WriteLine($"uint8_t padding_[{type.TypeDef.ClassLayout.ClassSize} - std::max({{{string.Join(", ", fieldSize)}}})];");
                    }
                    else
                    {
                        writer.Ident(ident + 1).WriteLine($"uint8_t padding_[{type.TypeDef.ClassLayout.ClassSize} - ({string.Join(" + ", fieldSize)})];");
                    }
                }

                if (type.TypeDef.IsExplicitLayout)
                {
                    writer.Ident(ident).WriteLine("};");
                    ident -= 1;
                }
            }

            writer.WriteLine();

            foreach (var method in type.TypeDef.Methods)
            {
                if (method.IsStaticConstructor)
                {
                    hasStaticMember = true;
                }
                else if (!method.IsAbstract)
                {
                    WriteMethodDeclare(writer, ident + 1, method);
                }
            }

            if (type.TypeDef.IsPrimitive)
            {
                writer.WriteLine();
                writer.Ident(ident + 1).WriteLine($"NATSU_PRIMITIVE_IMPL_{type.TypeDef.Name.ToUpperInvariant()}");
            }

            if (type.TypeDef.IsEnum)
            {
                writer.WriteLine();
                writer.Ident(ident + 1).WriteLine($"NATSU_ENUM_IMPL_{type.TypeDef.GetEnumUnderlyingType().TypeName.ToUpperInvariant()}({type.Name})");
                if (type.TypeDef.CustomAttributes.Any(x => x.TypeFullName == "System.FlagsAttribute"))
                    writer.Ident(ident + 1).WriteLine($"NATSU_ENUM_FLAG_OPERATORS({type.Name})");
            }

            if (type.TypeDef.ToTypeSig().ElementType == ElementType.Object)
            {
                writer.WriteLine();
                writer.Ident(ident + 1).WriteLine($"NATSU_OBJECT_IMPL");
            }

            if (type.TypeDef.FullName == "System.ValueType")
            {
                writer.WriteLine();
                writer.Ident(ident + 1).WriteLine($"NATSU_VALUETYPE_IMPL");
            }

            if (type.TypeDef == _szArrayType?.TypeDef)
            {
                writer.WriteLine();
                writer.Ident(ident + 1).WriteLine($"NATSU_SZARRAY_IMPL");
            }

            // Static
            if (hasStaticMember)
            {
                writer.WriteLine();
                writer.Ident(ident + 1).WriteLine("struct Static;");
            }

            writer.Ident(ident).WriteLine("};");

            // Static
            if (hasStaticMember)
            {
                writer.WriteLine();
                WriteStatic(writer, ident, type);
            }

            foreach (var ns in nss)
                writer.Write("} ");
            writer.WriteLine();
        }

        private void WriteTypeInfo(StreamWriter writer, int ident, TypeDesc type)
        {
            writer.Ident(ident).WriteLine($"struct TypeInfo");
            writer.Ident(ident).WriteLine("{");
            // IsValueType
            writer.Ident(ident + 1).WriteLine($"static constexpr bool IsValueType = {type.TypeDef.IsValueType.ToString().ToLower()};");
            // IsEnum
            writer.Ident(ident + 1).WriteLine($"static constexpr bool IsEnum = {type.TypeDef.IsEnum.ToString().ToLower()};");

            writer.Ident(ident).WriteLine("};");
        }

        private void WriteVTableDeclare(StreamWriter writer, int ident, TypeDesc type)
        {
            writer.Ident(ident).Write($"struct VTable");
            if (!type.TypeDef.IsInterface)
            {
                var baseSb = new StringBuilder();
                baseSb.Append("::natsu::vtable_class<");
                var baseType = GetBaseType(type.TypeDef);
                if (baseType == null)
                    baseSb.Append("natsu::clr_vtable");
                else
                    baseSb.Append($"typename {TypeUtils.EscapeTypeName(baseType, cppBasicType: true)}::VTable");

                foreach (var iface in type.TypeDef.Interfaces)
                {
                    var ifaceType = iface.Interface;
                    baseSb.Append($", typename {TypeUtils.EscapeTypeName(ifaceType, cppBasicType: true)}::VTable");
                }

                baseSb.Append(">");

                writer.WriteLine($": public {baseSb}");
                writer.Ident(ident).WriteLine("{");
                writer.Ident(ident + 1).WriteLine($"using base_t = {baseSb};");
                writer.WriteLine();
            }
            else
            {
                writer.WriteLine();

                writer.Ident(ident).WriteLine("{");
            }

            WriteVTableCtor(writer, type, ident + 1);
            writer.WriteLine();

            WriteVTableOverrideImpl(writer, type, ident + 1);
            writer.WriteLine();

            foreach (var method in type.TypeDef.Methods)
            {
                if (!method.IsInstanceConstructor && !method.IsStatic)
                    WriteVTableMethodDeclare(writer, ident + 1, method);
            }

            writer.Ident(ident).WriteLine("};");
        }

        private void WriteStatic(StreamWriter writer, int ident, TypeDesc type)
        {
            if (type.TypeDef.HasGenericParameters)
            {
                var typeNames = type.TypeDef.GenericParameters.Select(x => "class " + x.Name.String).ToList();
                writer.Ident(ident).WriteLine($"template <{string.Join(", ", typeNames)}> ");
            }

            writer.Ident(ident).Write($"struct {type.Name}");
            if (type.TypeDef.HasGenericParameters)
            {
                var typeNames = type.TypeDef.GenericParameters.Select(x => x.Name.String).ToList();
                writer.Ident(ident).WriteLine($"<{string.Join(", ", typeNames)}> ");
            }

            writer.WriteLine("::Static");
            writer.Ident(ident).WriteLine("{");

            foreach (var field in type.TypeDef.Fields)
            {
                if (field.IsStatic && !field.HasConstant)
                    WriteField(writer, ident + 1, field, true);
            }

            writer.WriteLine();

            foreach (var method in type.TypeDef.Methods)
            {
                if (method.IsStaticConstructor)
                    WriteMethodDeclare(writer, ident + 1, method);
            }

            writer.Ident(ident).WriteLine("};");
        }

        private void WriteField(StreamWriter writer, int ident, FieldDef value, bool isStatic = false)
        {
            string prefix = string.Empty;
            if (value.IsStatic && !isStatic)
                prefix = "static ";
            bool isExplicit = value.DeclaringType.IsExplicitLayout;

            writer.Ident(ident);
            if (isExplicit)
                writer.Write("struct { ");
            if (value.FieldOffset is uint offset && offset != 0)
                writer.Write($"uint8_t pad_{value.Rid}_[{offset}]; ");

            writer.Write($"{prefix}{TypeUtils.EscapeVariableTypeName(value.FieldType, value.DeclaringType)} {TypeUtils.EscapeIdentifier(value.Name)}");
            if (value.InitialValue != null)
                writer.Write($" = natsu::bit_init<{TypeUtils.EscapeVariableTypeName(value.FieldType, value.DeclaringType)}, {value.InitialValue.Length}>({{{string.Join(", ", value.InitialValue)}}})");
            writer.Write(';');
            if (isExplicit)
                writer.Write(" };");
            writer.WriteLine();
        }

        private void WriteMethodDeclare(TextWriter writer, int ident, MethodDef method)
        {
            var methodGens = new List<string>();

            if (method.HasGenericParameters)
                methodGens.AddRange(method.GenericParameters.Select(x => x.Name.String));

            if (methodGens.Any())
                writer.Ident(ident).WriteLine($"template <{string.Join(", ", methodGens.Select(x => "class " + x))}>");
            if (!method.IsStaticConstructor)
                writer.Ident(ident).Write("static " + TypeUtils.EscapeVariableTypeName(method.ReturnType) + " ");
            else
                writer.Ident(ident);
            writer.Write(TypeUtils.EscapeMethodName(method, hasParamType: false, hasExplicit: true) + "(");
            WriteParameterList(writer, method.Parameters);
            writer.WriteLine($");");
        }

        private void WriteParameterList(TextWriter writer, ParameterList parameters, bool hasType = true, bool isVTable = false)
        {
            var index = 0;
            var method = parameters.Method;
            foreach (var param in parameters)
            {
                if (hasType)
                {
                    if (isVTable && method.IsVirtual && param.IsHiddenThisParameter)
                        writer.Write("::natsu::gc_obj_ref<::System_Private_CoreLib::System::Object>");
                    else
                        writer.Write(TypeUtils.EscapeVariableTypeName(param.Type, hasGen: 1) + " ");
                }

                var paramName = param.IsHiddenThisParameter ? "_this" : param.ToString();
                if (!hasType && isVTable && method.IsVirtual && param.IsHiddenThisParameter)
                {
                    if (method.DeclaringType.IsValueType)
                    {
                        writer.Write($"::natsu::unbox_exact<{TypeUtils.EscapeTypeName(method.DeclaringType, cppBasicType: true)}>({TypeUtils.EscapeIdentifier(paramName)})");
                    }
                    else
                    {
                        writer.Write($"{TypeUtils.EscapeIdentifier(paramName)}.cast<{TypeUtils.EscapeTypeName(method.DeclaringType, cppBasicType: true)}>()");
                    }
                }
                else
                {
                    writer.Write(TypeUtils.EscapeIdentifier(paramName));
                }

                if (index++ != parameters.Count - 1)
                    writer.Write(", ");
            }
        }

        private void WriteConstantField(StreamWriter writer, int ident, FieldDef value)
        {
            string prefix = string.Empty;
            if (value.IsStatic)
                prefix = "static ";

            if (value.ElementType == ElementType.String)
            {
                var str = (string)value.Constant.Value;
                writer.Ident(ident).WriteLine($"static const constexpr auto _storage_{TypeUtils.EscapeIdentifier(value.Name)} = ::natsu::make_string_literal(uR\"NS({str})NS\");");

                writer.Ident(ident).WriteLine($"inline {prefix}::natsu::gc_obj_ref<::System_Private_CoreLib::System::String> {TypeUtils.EscapeIdentifier(value.Name)} = _storage_{TypeUtils.EscapeIdentifier(value.Name)}.get();");

            }
            else
            {
                writer.Ident(ident).WriteLine($"{prefix}constexpr {TypeUtils.GetConstantTypeName(value.ElementType)} {TypeUtils.EscapeIdentifier(value.Name)} = {TypeUtils.LiteralConstant(value.Constant.Value)};");
            }
        }
        #endregion

        private void WriteTypeMethodsBody(TextWriter writer, bool inHeader)
        {
            foreach (var type in _sortedTypeDescs)
            {
                WriteTypeMethodBody(writer, 0, type, inHeader);
            }
        }

        private void WriteTypeMethodBody(TextWriter writer, int ident, TypeDesc type, bool inHeader)
        {
            if (!type.TypeDef.IsAbstract && !type.TypeDef.IsInterface)
            {
                if (inHeader == type.TypeDef.HasGenericParameters)
                    WriteVTableRuntimeTypeMethodBody(writer, ident, type);
            }

            foreach (var method in type.TypeDef.Methods)
            {
                if (inHeader == (type.TypeDef.HasGenericParameters || method.HasGenericParameters))
                {
                    if (!method.IsInstanceConstructor && !method.IsStatic)
                    {
                        WriteVTableMethodBody(writer, ident, method);
                        writer.WriteLine();
                    }

                    if (!method.IsAbstract && !method.IsInternalCall
                        && (method.HasBody || method.IsRuntime))
                    {
                        WriteMethodBody(writer, ident, method);
                        writer.WriteLine();
                    }
                }
            }
        }

        private void WriteMethodBody(TextWriter writer, int ident, MethodDef method)
        {
            writer.Ident(ident);
            var typeGens = new List<string>();
            var methodGens = new List<string>();

            if (method.DeclaringType.HasGenericParameters)
                typeGens.AddRange(method.DeclaringType.GenericParameters.Select(x => x.Name.String));
            if (method.HasGenericParameters)
                methodGens.AddRange(method.GenericParameters.Select(x => x.Name.String));

            if (typeGens.Any())
                writer.WriteLine($"template <{string.Join(", ", typeGens.Select(x => "class " + x))}>");
            if (methodGens.Any())
                writer.WriteLine($"template <{string.Join(", ", methodGens.Select(x => "class " + x))}>");

            if (!method.IsStaticConstructor)
                writer.Write(TypeUtils.EscapeVariableTypeName(method.ReturnType) + " ");
            writer.Write(TypeUtils.EscapeTypeName(method.DeclaringType, hasModuleName: false));
            if (method.IsStaticConstructor)
                writer.Write("::Static");
            writer.Write("::" + TypeUtils.EscapeMethodName(method, hasParamType: false, hasExplicit: true) + "(");
            WriteParameterList(writer, method.Parameters);
            writer.WriteLine(")");
            writer.Ident(ident).WriteLine("{");
            if (method.HasBody)
                WriteILBody(writer, ident + 1, method);
            else if (method.IsRuntime)
                WriteRuntimeBody(writer, ident + 1, method);
            else
                throw new NotSupportedException();
            writer.Ident(ident).WriteLine("}");
            writer.Flush();
        }

        private void WriteVTableCtor(StreamWriter writer, TypeDesc type, int ident)
        {
            bool firstInit = true;
            writer.Ident(ident).WriteLine("constexpr VTable()");

            foreach (var method in type.TypeDef.Methods)
            {
                if (!method.IsInstanceConstructor && !method.IsStatic)
                {
                    if (method.IsVirtual && method.IsNewSlot && !method.Name.Contains("."))
                    {
                        if (firstInit)
                        {
                            writer.Ident(ident + 1).Write(": ");
                            firstInit = false;
                        }
                        else
                        {
                            writer.Ident(ident + 1).Write(", ");
                        }

                        writer.Write(TypeUtils.EscapeMethodName(method));
                        writer.WriteLine("(_imp_" + TypeUtils.EscapeMethodName(method) + ")");
                    }
                }
            }

            writer.Ident(ident).WriteLine("{");

            WriteVTableTypeInfo(writer, type, ident + 1);
            writer.WriteLine();

            foreach (var method in type.TypeDef.Methods)
            {
                if (!method.IsInstanceConstructor && !method.IsStatic)
                {
                    if (method.IsVirtual)
                    {
                        if (method.Name.Contains("."))
                        {
                            foreach (var ov in method.Overrides)
                            {
                                writer.Ident(ident + 1).Write(TypeUtils.EscapeTypeName(ov.MethodDeclaration.DeclaringType, cppBasicType: true));
                                writer.Write("::VTable::");
                                writer.Write(TypeUtils.EscapeMethodName(ov.MethodDeclaration));
                                writer.Write(" = ");
                                writer.WriteLine("_imp_" + TypeUtils.EscapeMethodName(method, hasExplicit: true) + ";");
                            }
                        }
                        else if (!type.TypeDef.IsInterface)
                        {
                            writer.Ident(ident + 1).Write("base_t::override_vfunc");
                            writer.Write("(R\"NS(" + method.Name + ")NS\", ");
                            writer.WriteLine("_imp_" + TypeUtils.EscapeMethodName(method) + ");");
                        }
                    }
                }
            }

            writer.Ident(ident).WriteLine("}");
        }

        private void WriteVTableTypeInfo(StreamWriter writer, TypeDesc type, int ident)
        {
            // array
            if (type.TypeDef.FullName == "System.SZArray`1")
            {
                writer.Ident(ident).WriteLine("ElementSize = sizeof(T);");
            }
        }

        private void WriteVTableOverrideImpl(StreamWriter writer, TypeDesc type, int ident)
        {
            writer.Ident(ident).WriteLine("template <class TFunc>");
            writer.Ident(ident).WriteLine("constexpr void override_vfunc_impl(std::string_view name, TFunc func)");
            writer.Ident(ident).WriteLine("{");

            foreach (var method in type.TypeDef.Methods)
            {
                if (!method.IsInstanceConstructor && !method.IsStatic)
                {
                    if (method.IsVirtual && method.IsNewSlot && !method.Name.Contains("."))
                    {
                        writer.Ident(ident + 1).WriteLine($@"if (name == ""{method.Name}""sv)");
                        writer.Ident(ident + 2).WriteLine($@"if constexpr (std::is_same_v<TFunc, decltype({TypeUtils.EscapeMethodName(method)})>)");
                        writer.Ident(ident + 3).WriteLine(TypeUtils.EscapeMethodName(method) + " = func;");
                    }
                }
            }

            writer.Ident(ident).WriteLine("}");

            if (!type.TypeDef.IsAbstract && !type.TypeDef.IsInterface)
            {
                writer.Ident(ident).WriteLine();
                writer.Ident(ident).WriteLine("::natsu::gc_obj_ref<::System_Private_CoreLib::System::RuntimeType> runtime_type() const noexcept override;");
            }
        }

        private void WriteVTableMethodDeclare(TextWriter writer, int ident, MethodDef method)
        {
            var methodGens = new List<string>();

            if (method.HasGenericParameters)
                methodGens.AddRange(method.GenericParameters.Select(x => x.Name.String));

            if (methodGens.Any())
                writer.Ident(ident).WriteLine($"template <{string.Join(", ", methodGens.Select(x => "class " + x))}>");

            if (method.IsVirtual)
            {
                if (method.HasGenericParameters)
                    throw new NotSupportedException("Virtual generic methods is not supported");

                if (method.IsNewSlot)
                {
                    // Explicit override
                    if (!method.Name.Contains("."))
                    {
                        writer.Ident(ident).Write(TypeUtils.EscapeVariableTypeName(method.ReturnType) + " (*");
                        writer.Write(TypeUtils.EscapeMethodName(method) + ")(");
                        WriteParameterList(writer, method.Parameters, isVTable: true);
                        writer.WriteLine(");");
                    }
                }

                writer.Ident(ident).Write("static " + TypeUtils.EscapeVariableTypeName(method.ReturnType) + " _imp_");
                writer.Write(TypeUtils.EscapeMethodName(method, hasExplicit: true) + "(");
                WriteParameterList(writer, method.Parameters, isVTable: true);
                writer.WriteLine(");");
            }
            else
            {
                writer.Ident(ident).Write(TypeUtils.EscapeVariableTypeName(method.ReturnType) + " ");
                writer.Write(TypeUtils.EscapeMethodName(method, hasParamType: true) + "(");
                WriteParameterList(writer, method.Parameters, isVTable: true);
                writer.WriteLine(") const;");
            }

            writer.Flush();
        }

        private void WriteVTableRuntimeTypeMethodBody(TextWriter writer, int ident, TypeDesc type)
        {
            writer.Ident(ident);
            var typeGens = new List<string>();

            if (type.TypeDef.HasGenericParameters)
                typeGens.AddRange(type.TypeDef.GenericParameters.Select(x => x.Name.String));

            if (typeGens.Any())
                writer.WriteLine($"template <{string.Join(", ", typeGens.Select(x => "class " + x))}>");

            writer.Write("::natsu::gc_obj_ref<::System_Private_CoreLib::System::RuntimeType> ");
            writer.Write(TypeUtils.EscapeTypeName(type.TypeDef, hasModuleName: false));
            writer.WriteLine("::VTable::runtime_type() const noexcept");
            writer.Ident(ident).WriteLine("{");
            writer.Ident(ident + 1).Write($"return ::natsu::runtime_type_holder<");
            writer.Write(TypeUtils.EscapeTypeName(type.TypeDef, hasModuleName: false));
            writer.WriteLine(">::get();");
            writer.Ident(ident).WriteLine("}");
            writer.WriteLine();

            writer.Flush();
        }

        private void WriteVTableMethodBody(TextWriter writer, int ident, MethodDef method)
        {
            writer.Ident(ident);
            var typeGens = new List<string>();
            var methodGens = new List<string>();

            if (method.DeclaringType.HasGenericParameters)
                typeGens.AddRange(method.DeclaringType.GenericParameters.Select(x => x.Name.String));
            if (method.IsVirtual && method.HasGenericParameters)
                throw new NotSupportedException("Virtual generic methods is not supported");
            if (method.HasGenericParameters)
                methodGens.AddRange(method.GenericParameters.Select(x => x.Name.String));

            if (typeGens.Any())
                writer.WriteLine($"template <{string.Join(", ", typeGens.Select(x => "class " + x))}>");
            if (methodGens.Any())
                writer.WriteLine($"template <{string.Join(", ", methodGens.Select(x => "class " + x))}>");

            writer.Write(TypeUtils.EscapeVariableTypeName(method.ReturnType) + " ");
            writer.Write(TypeUtils.EscapeTypeName(method.DeclaringType, hasModuleName: false));
            writer.Write("::VTable::");
            if (method.IsVirtual)
                writer.Write("_imp_");
            writer.Write(TypeUtils.EscapeMethodName(method, hasParamType: true, hasExplicit: true) + "(");
            WriteParameterList(writer, method.Parameters, isVTable: true);
            if (method.IsVirtual)
                writer.WriteLine(")");
            else
                writer.WriteLine(") const");
            writer.Ident(ident).WriteLine("{");
            if (method.IsAbstract)
            {
                writer.Ident(ident + 1).WriteLine("::natsu::pure_call();");
            }
            else
            {
                writer.Ident(ident + 1).Write("return ");
                writer.Write(TypeUtils.EscapeTypeName(method.DeclaringType));
                writer.Write("::" + TypeUtils.EscapeMethodName(method, hasParamType: false, hasExplicit: true) + "(");
                WriteParameterList(writer, method.Parameters, hasType: false, isVTable: true);
                writer.WriteLine(");");
            }
            writer.Ident(ident).WriteLine("}");

            writer.Flush();
        }

        private void WriteILBody(TextWriter writer, int ident, MethodDef method)
        {
            var body = method.Body;

            foreach (var local in body.Variables)
            {
                WriteLocal(local, writer, ident, method);
            }

            if (body.ExceptionHandlers.Any())
            {

            }

            var importer = new ILImporter(_corLibTypes, method, writer, ident) { UserStrings = _userStrings, ModuleName = TypeUtils.EscapeModuleName(_module.Assembly) };
            importer.ImportNormalBlocks();
            importer.ImportExceptionBlocks();
            importer.Gencode();
        }

        private void WriteRuntimeBody(TextWriter writer, int ident, MethodDef method)
        {
            if (method.DeclaringType.IsDelegate)
            {
                if (method.IsInstanceConstructor)
                {
                    writer.Ident(ident).WriteLine($"_this->_target = object;");
                    writer.Ident(ident).WriteLine($"_this->_methodPtr = method;");
                }
                else if (method.Name == "Invoke")
                {
                    writer.Ident(ident).Write($"typedef {TypeUtils.EscapeVariableTypeName(method.ReturnType)}(*method_t)(::natsu::gc_obj_ref<::System_Private_CoreLib::System::Object>");
                    foreach (var param in method.Parameters.Skip(1))
                        writer.Write(", " + TypeUtils.EscapeVariableTypeName(param.Type, hasGen: 1));
                    writer.WriteLine(");");

                    writer.Ident(ident).WriteLine($"if (!_this->_invocationList)");
                    writer.Ident(ident).WriteLine("{");
                    writer.Ident(ident + 1).Write($"return reinterpret_cast<method_t>((intptr_t)_this->_methodPtr)(_this->_target");
                    foreach (var param in method.Parameters.Skip(1))
                    {
                        var paramName = param.IsHiddenThisParameter ? "_this" : param.ToString();
                        writer.Write(", " + paramName);
                    }
                    writer.WriteLine($");");
                    writer.Ident(ident).WriteLine("}");

                    writer.Ident(ident).WriteLine($"else");
                    writer.Ident(ident).WriteLine("{");
                    if (method.HasReturnType)
                        writer.Ident(ident + 1).WriteLine($"{TypeUtils.EscapeVariableTypeName(method.ReturnType)} result;");
                    writer.Ident(ident + 1).WriteLine("for (auto d : *_this->_invocationList)");
                    writer.Ident(ident + 1).WriteLine("{");
                    writer.Ident(ident + 2).WriteLine($"auto typed_d = d.template cast<{TypeUtils.EscapeTypeName(method.DeclaringType)}>();");
                    if (method.HasReturnType)
                        writer.Ident(ident + 2).Write($"result = reinterpret_cast<method_t>((intptr_t)typed_d->_methodPtr)(typed_d->_target");
                    else
                        writer.Ident(ident + 2).Write($"reinterpret_cast<method_t>((intptr_t)typed_d->_methodPtr)(typed_d->_target");
                    foreach (var param in method.Parameters.Skip(1))
                    {
                        var paramName = param.IsHiddenThisParameter ? "_this" : param.ToString();
                        writer.Write(", " + paramName);
                    }
                    writer.WriteLine($");");
                    writer.Ident(ident + 1).WriteLine("}");
                    if (method.HasReturnType)
                        writer.Ident(ident + 1).WriteLine($"return result;");
                    writer.Ident(ident).WriteLine("}");
                }
                else if (method.Name == "BeginInvoke")
                {
                    writer.Ident(ident).WriteLine($"::natsu::pure_call();");
                }
                else if (method.Name == "EndInvoke")
                {
                    writer.Ident(ident).WriteLine($"::natsu::pure_call();");
                }
                else
                {
                    throw new NotSupportedException();
                }
            }
            else
            {
                throw new NotSupportedException();
            }
        }

        private void WriteLocal(Local local, TextWriter writer, int ident, MethodDef method)
        {
            writer.Ident(ident).WriteLine($"{TypeUtils.EscapeVariableTypeName(local.Type)} {TypeUtils.GetLocalName(local, method)};");
        }

        class TypeDesc
        {
            public TypeDef TypeDef { get; }

            public UTF8String Name { get; set; }

            public string QualifiedName { get; set; }

            public Dictionary<UTF8String, TypeDesc> Nested { get; } = new Dictionary<UTF8String, TypeDesc>();

            public HashSet<TypeDesc> UsedTypes { get; } = new HashSet<TypeDesc>();

            public HashSet<TypeDesc> UsedByTypes { get; } = new HashSet<TypeDesc>();

            public TypeDesc(TypeDef typeDef)
            {
                TypeDef = typeDef;
                Name = TypeUtils.EscapeTypeName(typeDef.FullName);
                QualifiedName = Name;
            }

            public override string ToString()
            {
                return QualifiedName;
            }
        }
    }

    internal static class Extensions
    {
        public static TextWriter Ident(this TextWriter writer, int ident)
        {
            for (int i = 0; i < ident; i++)
                writer.Write("    ");
            return writer;
        }
    }
}
